#!/usr/bin/env bash

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2, June 1991.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/gpl-2.0.html>,
# or in pdf format at <http://www.dhampir.no/stuff/private/gpl-2.0.pdf>

# Copyright 2012-2014 - Øyvind 'bolt' Hvidsten   <bolt@dhampir.no>

# Description:
#
# telenorsms is a script that connects to the Norwegian cell operator Telenor and sends SMS messages
#
# It requires curl or wget, xxd, tr, cat and sed. "tempfile" is also recommended, though the script should manage
# to generate tempfiles itself.
#
# Your username (email) and password for your Telenor account need to be stored in ~/.telenorsms on two
# separate lines:
# ------------------------------------------
# username=me@provider.com
# password=awesome1
# grabber=/home/me/bin/grabber
# ------------------------------------------
# As shown above, it is also strongly recommended to indicate the path to the "grabber" library,
# which should be available where this script was downloaded from. Grabber does not need to be executable.
#
# You must have sent at least one message through the online interface, as it will ask you to verify your
# cell phone number by entering a code received by SMS, the first time you use it.
#
# telenorsms supports using numbers and names stored in your online address book in the SMS section of
# Telenor's pages. Messages can be sent to stored numbers using complete names or partial numbers.
#
# Examples:
#   ./telenorsms Joe Hey man, how's it going?
#   ./telenorsms +4711223344 This is a message.
#   ./telenorsms 1122 This is a message.
#
# Comments welcome, improvements/patches twice as welcome.
#

# Version history:
#
# 2009-02-03: Initial version - ivc
# 2009-08-19: Support for local phonebook and various bug fixes - Håvard Gulldahl
# 2012-07-29: Rewrite for Bash 4 with support for new "MineSider" setup and online phonebook - Øyvind 'bolt' Hvidsten
# 2013-04-07: Minor style changes and comment cleanup - Øyvind 'bolt' Hvidsten
# 2013-04-20: Updated to comply with changes on Telenor's website
# 2013-06-15: Retry 3 times because of random cases of Ubuntu bug #965371 occuring on Telenor's server:
#             GnuTLS: A TLS packet with unexpected length was received.
#             Unable to establish SSL connection.
# 2014-04-04: Updated to comply with changes on Telenor's website
# 2014-08-26: Support for complaining about "technical error" on Telenor's website
# 2014-08-27: Support for the -d (dry run) option, testing most of the script without sending a message
# 2014-08-28: Support for the -a (force address book) option, used during debugging
# 2014-10-01: Spelling error fix
# 2014-10-18: Rewrote URL grabbing to use curl, as Telenor screwed up their TLS negotiation
#             Use of wget can still be forced by adding [-w]
#             Added several debugging features
#             Note from debugging: URL encoding the "+" sign in "Logg+inn" breaks Telenor's form
# 2014-10-19: Pushed the HTTP grabbing functionality to a separate file in order to unify the methods used
#             across several scripts I'm maintaining that all have similar functionality.
#             Deeply sorry for any inconvenience caused.
# 2014-10-24: They changed the text "gjenstående SMS" to "gjenstående gratis SMS" for no reason. OK.
# 2015-01-04: Added detection of the SMS counter breaking - no longer exiting with non-zero unless debugging
# 2015-05-01: Minimal fix to support new link format for the login page
# 2015-05-16: Updated to comply with changes to grabber
#


_scriptname="telenorsms"
set -e
set -u


# Options
dryrun=false
DEBUG=false
forceaddressbook=false
GRABBER="curl"
OPTIND=1
while getopts ":adDw" opt; do
	case "$opt" in
		a) forceaddressbook=true ;;
		d) dryrun=true ;;
		D) DEBUG=true ;;
		w) GRABBER="wget" ;;
		[?])
			echo "Unknown option -${OPTARG}" >&2
			exit 1
		;;
		:) 
			echo "Option -${OPTARG} requires an argument" >&2
			exit 1
		;;
	esac
done
shift $((OPTIND-1))
if [[ "${1:-}" = "--" ]]; then shift; fi


# Settings
config="$HOME/.${_scriptname}"


# Grabber
grabber=$(grep -m 1 "^grabber=" $config 2>/dev/null | cut -d = -f 2);
[[ -n "$grabber" ]] || grabber="/cathedral/src/lib/grabber" # alt path
[[ -e "$grabber" ]] || grabber="$HOME/.grabber"             # default path
if ! [[ -e "$grabber" ]]; then
	echo "grabber library was not found" >&2
	echo "Please download \"grabber\" from the same place you got this script," >&2
	echo "Put it at $grabber or point at it in $config." >&2
	exit 2
fi
source "$grabber"


# Config
username=$(grep -m 1 "^username=" $config 2>/dev/null | cut -d = -f 2 | encode);
password=$(grep -m 1 "^password=" $config 2>/dev/null | cut -d = -f 2 | encode);
[[ -z "${1:-}" ]] || { recipient=$1 && shift; }
[[ -z "${1:-}" ]] || { message=$(encode "$@"); }


# Sanity
if [[ -z "${username:-}" ]] || [[ -z "${password:-}" ]]; then
	echo "Username or password unset. please put both in $config!" >&2
	exit 1
fi
if [[ -z "${recipient:-}" ]] || [[ -z "${message:-}" ]]; then
	echo -e "Missing required arguments!\nUsage: $_scriptname [-d] <name|number> <text message>" >&2
	exit 0
fi


# Get cookie
grabber_task "Getting cookie"
grab "https://www.telenor.no/privat/minesider/logginnfelles.cms"
assert 'action="/privat/minesider/logginnfelles.cms'


# Login
grabber_task "Logging in"
up=false
while true; do
	grab_data="usr_name=$username&usr_password=$password&lbAction=Logg+inn" \
	grab_ref="https://www.telenor.no/privat/minesider/logginnfelles.cms" \
	grab "https://www.telenor.no/privat/minesider/logginnfelles.cms"
	if found 'SEND_SMS'; then
		echo "OK"
		break
	elif found 'action="/privat/minesider/minprofil/oppdatereMinProfil.cms"'; then
		if ! $up; then
			echo "Profile update needed!"
			grabber_task "Updating profile"
			grab_data="confirmProfile=true&lbAction=Bekreft" \
			grab_ref="https://www.telenor.no/privat/minesider/minprofil/oppdatereMinProfil.cms" \
			grab "https://www.telenor.no/privat/minesider/minprofil/oppdatereMinProfil.cms"
			up=true
		else
			grabber_fail
		fi
	else
		grabber_fail
	fi
done


# Mandatory SMS page get...
grabber_task "SMS page get"
grab_ref="https://www.telenor.no/privat/minesider/minside/minSidePage.cms" \
grab "https://www.telenor.no/privat/minesider/norm/win/sms/send.do"
if found '<h1>Teknisk feil</h1>'; then
	grabber_fail '"Teknisk feil" - Have you activated SMS from Web yet?'
fi
assert 'action="/norm/win/sms/send/process.do"'


# Get the address list
grabber_task "Getting address list"
grab_ref="https://www.telenor.no/privat/minesider/minside/minSidePage.cms" \
grab "https://telenormobil.no/norm/sms/async/addresslist.do"
grabber_ok
grabber_task "Parsing..."
names=()
numbers=()
i=0
while read -r line; do
	# begin stupid address book format
	[[ "$line" = *'<option value="'* ]] || continue
	line=${line#*'<option value="'}
	number=${line%%\"*}
	line=${line#*'>'}
	name=${line%(*}
	# end stupid address book format
	read -r number <<<"$number"
	read -r name <<<"$name"
	if [[ -n "$name" ]] && [[ -n "$number" ]]; then
		names[i]=$name
		numbers[i]=$number
		(( ++i ))
	fi
done <"$f_stdout"
grabber_ok
echo "Read ${#names[@]:-} entries from address list."


# Translate recipients given as names to numbers in the address list
for (( i=0; i<${#names[@]:-}; i++ )); do
	if [[ "${recipient,,}" = "${names[i],,}" ]]; then
		recipient=${numbers[i]}
		break
	fi
done


# Translate recipients given as partial numbers (or translated above) to full numbers and announce
match=false
for (( i=0; i<${#numbers[@]:-}; i++ )); do
	if
		[[ "$recipient" = *"${numbers[i]}"* ]] ||
		[[ "${numbers[i]}" = *"$recipient"* ]]
	then
		recipient=${numbers[i]}
		echo "Recipient recognized as ${names[i]} (${numbers[i]})"
		match=true
		break
	fi
done
if ! $match; then
	echo "Recipient not in address book: $recipient"
	if $forceaddressbook; then
		grabber_fail "Address book broken?"
	fi
fi


# Send SMS
if ! $dryrun; then
	grabber_task "Sending SMS"
	grab_head="Content-Type: application/x-www-form-urlencoded; charset=utf-8" \
	grab_data="toAddress=$(encode "$recipient")&message=$message&b_send=b_send" \
	grab "https://telenormobil.no/norm/win/sms/send/process.do"
	if found '<td>Sendt</td>'; then
		echo "Sent!"
	else
		grabber_fail
	fi
fi


# SMS left count
grabber_task "Getting number of free messages remaining"
grab_ref="https://www.telenor.no/privat/minesider/minside/minSidePage.cms" \
grab "https://telenormobil.no/norm/win/sms/send/popup/counter.do"
if
	free=$(grep 'Antall gjenstående gratis SMS: ' "$f_stdout") &&
	free=${free#*'Antall gjenstående gratis SMS: '} &&
	free=${free%% *} &&
	[[ -n "$free" ]]
then
	echo "$free"
	echo "Telenor updates the number of free messages remaining every 10 minutes."
elif
	found "Klarte ikke å hente antall gjenstående gratis SMS"
then
	echo "Telenor broke the SMS counter again."
	! $DEBUG || exit 1
else
	grabber_fail
fi
